package brian.android.mvc.activity;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;

import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.view.View.OnClickListener;
import brian.android.mvc.activity.config.ActivityConstant;
import brian.android.mvc.activity.config.Configuration;
import brian.android.mvc.annotation.Event;
import brian.android.mvc.annotation.Inject;
import brian.android.mvc.annotation.ViewIdNotExistsException;
import brian.android.mvc.bean.BeanFactory;
import brian.android.mvc.event.EventHandler;
import brian.android.mvc.event.EventHandlerFactory;
import brian.android.mvc.exception.BeanCreationException;
import brian.android.mvc.exception.IllegalActivityMethodNameException;
import brian.android.mvc.exception.InitActivityFailException;
import brian.android.mvc.exception.NoBeanIdException;

public abstract class AbstractActivity extends Activity {
	private static final String[] EVENT_NAMES = {"onClick"};
	private static final String EMPTY = "";
	
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		
		try {
			initBeforeViewSet(savedInstanceState);
			setContentView(getContentView());
			initMembers();
			initEvents();
			initAfterViewSet(savedInstanceState);
		} catch (Exception e) {
			Log.e(this.getClass().getSimpleName(), e.getMessage(), e);
			throw new InitActivityFailException(e.getMessage(), e);
		}
	}

	protected abstract int getContentView() throws ViewIdNotExistsException;

	protected void initBeforeViewSet(Bundle savedInstanceState) {}

	protected void initAfterViewSet(Bundle savedInstanceState) {}

	protected void initMembers() throws NoBeanIdException, BeanCreationException {
		for (Field field : this.getClass().getDeclaredFields()) {
			injectView(field);
			injectMember(field);
		}
	}

	private void injectView(Field field) {
		if (View.class.isAssignableFrom(field.getType())) {
			View view = findViewByName(field.getName());
			
			if (view != null) {
				setFieldValue(field, view);
			} else {
				Log.w(this.getClass().getSimpleName(), "\"" + field.getName() + "\" is not exists in class \"" + this.getClass().getSimpleName() + "\". skip binding");
			}
		}
	}

	protected void injectMember(Field field) throws NoBeanIdException, BeanCreationException {
		Inject inject = field.getAnnotation(Inject.class);
		
		if (inject != null) {
			String beanId = getBeanId(field, inject);
			Object bean = BeanFactory.getBean(beanId);
			setFieldValue(field, bean);
		}
	}

	private void setFieldValue(Field field, Object bean) {
		boolean accessible = field.isAccessible();
		field.setAccessible(true);
		try {
			field.set(this, bean);
		} catch (Exception e) {
			Log.e(this.getClass().getSimpleName(), e.getMessage(), e);
		}
		field.setAccessible(accessible);
	}
	
	private View findViewByName(String viewName) {
		int id = findViewIdByName(viewName);
		if (id > 0) {
			return findViewById(id);
		} else {
			return null;
		}
	}


	private int findViewIdByName(String viewName) {
		Field fId = null;;
		try {
			fId = Configuration.getIdClass(this).getField(viewName);
		} catch (Exception e) {
			return -1;
		}
			
		try {
			return fId.getInt(Configuration.getIdClass(this));
		} catch (Exception e) {
			return -1;
		}
	}

	@SuppressWarnings("unchecked")
	protected void initEvents() {
		for (Method method : this.getClass().getDeclaredMethods()) {
			Event event = method.getAnnotation(Event.class);
			if (event != null) {
				List<View> views = getEventViews(event, method);
				List<Class> eventListeners = getEventListeners(event, method);
				
				for (View view : views) {
					if (view != null) {
						for (Class eventListener : eventListeners) {
							EventHandler handler = EventHandlerFactory.getEventHandler(eventListener);
							handler.setEvent(view, this, method);
						}
					} else {
						Log.w(this.getClass().getSimpleName(), "\"" + method.getName() + "\" event handler method has no matching view. skip binding");
					}
				}
			}
		}
	}


	@SuppressWarnings("unchecked")
	private List<Class> getEventListeners(Event event, Method method) {
		Class[] eventListeners = event.event();
		
		if (eventListeners.length > 0) {
			return Arrays.asList(eventListeners);
		} else {
			Class eventListener = getEventByMethodName(method.getName());
			return Arrays.asList(eventListener);
		}
	}

	@SuppressWarnings("unchecked")
	private Class getEventByMethodName(String methodName) {
		String eventName = getEventName(methodName);
		if (eventName != null) {
			if ("onClick".equals(eventName)) {
				return OnClickListener.class;
			}
			//TODO 다른 Event Prefix 들 정리해서 넣기 
		}
		return null;
	}

	private List<View> getEventViews(Event event, Method method) {
		int[] resourceIds = event.view();
		
		if (resourceIds.length > 0) {
			List<View> views = new ArrayList<View>();
			for (int resourceId : resourceIds) {
				View view = findViewById(resourceId);
				views.add(view);
			}
			return views;
		} else {
			String viewName = getEventViewName(method);
			if (viewName != null) {
				return Arrays.asList(findViewByName(viewName));
			} else {
				return Collections.emptyList();
			}
		}
	}

	private String getEventViewName(Method method) {
		String methodName = method.getName();
		String eventName = getEventName(methodName);
		
		if (eventName != null) {
			methodName = methodName.replace(eventName, "");
			return methodName.substring(0, 1).toLowerCase() + methodName.substring(1);
		}
		return null;
	}
	
	private String getEventName(String methodName) {
		for (String eventName : EVENT_NAMES) {
			if (methodName.startsWith(eventName)) {
				return eventName;
			}
		}
		return null;
	}

	private String getBeanId(Field field, Inject inject) {
		String beanId = null;
		if (EMPTY.equals(inject.value())) {
			beanId = field.getName();
		} else {
			beanId = inject.value();
		}
		return beanId;
	}

	@SuppressWarnings("unchecked")
	protected <T> T getExtraValueOrDefault(String id, Object defaultValue) {
		T value = getExtraValue(id);
		if (value != null) {
			return value;
		} else {
			return (T) defaultValue;
		}
	}
	
	@SuppressWarnings("unchecked")
	protected <T> T getExtraValue(String id) {
		Bundle extras = getIntent().getExtras();
		
		if (extras != null && extras.containsKey(id)) {
			return (T) extras.get(id);
		} else {
			return null;
		}
	}
	
	protected void redirectActivity(Class<? extends Activity> clazz, Object ... arguments) {
		try {
			redirectActivity(RESULT_OK, clazz, null, arguments);
		} catch (IllegalActivityMethodNameException e) {
			//TODO Exception
			Log.e(this.getClass().getSimpleName(), e.getMessage(), e);
		}
	}
	
	protected void redirectActivity(int result, Class<? extends Activity> clazz, Object ... arguments) {
		try {
			redirectActivity(result, clazz, null, arguments);
		} catch (IllegalActivityMethodNameException e) {
			//TODO Exception
			Log.e(this.getClass().getSimpleName(), e.getMessage(), e);
		}
	}
	
	/**
	 * {@code activityMethodName} Method 로 Activity를 호출한다. startActivityForResult를 통해 호출한다. 
	 * @param activityMethodName Activity를 호출 할 Method 명 
	 * @throws IllegalActivityMethodNameException activityMethodName 의 method 가 없을 경우 발생하는 예외 
	 * */
	protected void redirectActivity(String activityMethodName, Object ... arguments) throws IllegalActivityMethodNameException {
		redirectActivity(RESULT_OK, this.getClass(), activityMethodName, arguments);
	}
	
	/**
	 * {@code activityMethodName} Method 로 Activity를 호출한다. startActivityForResult를 통해 호출한다. 
	 * @param activityMethodName Activity를 호출 할 Method 명 
	 * @throws IllegalActivityMethodNameException activityMethodName 의 method 가 없을 경우 발생하는 예외 
	 * */
	protected void redirectActivity(int result, String activityMethodName, Object ... arguments) throws IllegalActivityMethodNameException {
		redirectActivity(result, this.getClass(), activityMethodName, arguments);
	}
	
	protected void redirectActivity(Class<? extends Activity> nextActivity, String methodname, Object ... arguments) throws IllegalActivityMethodNameException {
		redirectActivity(RESULT_OK, nextActivity, methodname, arguments);
	}
	/**
	 * {@code activityMethodName} Method 로 Activity를 호출한다. startActivityForResult를 통해 호출한다. 
	 * @param methodname Activity를 호출 할 Method 명 
	 * @throws IllegalActivityMethodNameException activityMethodName 의 method 가 없을 경우 발생하는 예외 
	 * */
	protected void redirectActivity(int result, Class<? extends Activity> nextActivity, String methodname, Object ... arguments) throws IllegalActivityMethodNameException {
		
		if (methodname != null && !isMethodExists(methodname, nextActivity)) {
			throw new IllegalActivityMethodNameException(methodname, nextActivity);
		}
		
		Intent data = createIntent(nextActivity, methodname);
		putIntentExtra(data, arguments);
		
		if (getCallingActivity() != null) {
			setResult(result, data);
			finish();
		} else {
			startActivityForResult(data, 0);
		}
	}
	
	private Intent createIntent(Class<? extends Activity> nextActivity, String methodName) {
		Intent data = null;
		if (nextActivity != null) {
			data = new Intent(this, nextActivity);
			data.putExtra(ActivityConstant.ACTIVITY_REDIRECT, true);
			data.putExtra(ActivityConstant.ACTIVITY_METHOD_NAME, methodName);
		} else {
			data = new Intent();
			data.putExtra(ActivityConstant.ACTIVITY_REDIRECT, false);
		}
		return data;
	}

	private boolean isMethodExists(String activityMethodName, Class<? extends Activity> activityClazz) {
		for (Method method : activityClazz.getMethods()) {
			if (activityMethodName.equals(method.getName())) {
				return true;
			}
		}
		return false;
	}

	private void putIntentExtra(Intent data, Object... keyValues) {
		if (keyValues != null) {
			if (keyValues.length % 2 != 0) {
				//TODO throw Paramenter Exception 
			}
			
			for (int i=0; i < (keyValues.length / 2); i++) {
				int keyIdx = i*2;
				int valIdx = keyIdx + 1;
				if (keyValues[valIdx] instanceof String) {
					data.putExtra((String) keyValues[keyIdx], (String) keyValues[valIdx]);
				} else if (keyValues[valIdx] instanceof Integer) {
					data.putExtra((String) keyValues[keyIdx], (Integer) keyValues[valIdx]);
				} else if (keyValues[valIdx] instanceof Long) {
					data.putExtra((String) keyValues[keyIdx], (Long) keyValues[valIdx]);
				} else if (keyValues[valIdx] instanceof Boolean) {
					data.putExtra((String) keyValues[keyIdx], (Boolean) keyValues[valIdx]);
				}
				// TODO putExtra other type
			}
		}
	}
	
	@Override
	protected void onActivityResult(int requestCode, int resultCode, Intent data) {
		if (data == null) {
			return;
		}
		
		Bundle extras = data.getExtras();
		if (extras.getBoolean(ActivityConstant.ACTIVITY_REDIRECT)) {
			startActivityForResult(data, 0);
		} else {
			onActivityResultInternal(requestCode, resultCode, data);
		}
	}
	
	/**
	 * onActivityResult 을 대체하는 메소드 
	 * onActivityResult 이 필요할 경우 override 하세요. 
	 * */
	protected void onActivityResultInternal(int requestCode, int resultCode, Intent data) {
		// do nothing
	}
}
